﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting.Silverlight;
using System.Windows.Browser;
using System.Windows.Markup;
using System.Json;
using System.Windows.Threading;
using GestaltUtil;

namespace Gestalt
{
    public class GestaltScriptApp : GestaltApp
    {

        //private string htmlId;
        //private object app;

        private IDictionary<string, string> parameters;
        Dictionary<object, object> englist = new Dictionary<object, object>();

        public GestaltScriptApp()
        {
            this.scriptParser = delegate() { ScanCodeBlocks(); };
        }


        public void ScanCodeBlocks()
        {
            var scriptTags = HtmlPage.Document.GetElementsByTagName("script");

            foreach (var tag in scriptTags)
            {
                HtmlElement script = (HtmlElement)tag;
                string lang = script.GetAttribute("language");
                if (lang == null)
                {
                    lang = script.GetAttribute("type");
                    if (lang != null) lang = lang.Replace("text/", "").Replace("application/", "");
                }

                string forId = script.CssClass;

                if (forId != "" && forId != htmlId && forId != "*") continue;  // if forId is blank, equivalent to *

                if (lang != null && (lang.ToLower() == "python" || lang.ToLower() == "ruby" || lang.ToLower() == "js"))
                {
                    string scriptSrc = script.GetAttribute("src");
                    if (scriptSrc == null || scriptSrc == "")
                    {
                        string scriptbody = (string)script.GetProperty("innerHTML");
                        ExecuteCode(GetScope(lang, englist), scriptbody);
                    }
                    else
                    {
                        // need to download the script
                        Uri src = new Uri(HtmlPage.Document.DocumentUri, scriptSrc);

                        WebClient scriptDownload = new WebClient();
                        scriptDownload.DownloadStringCompleted += new DownloadStringCompletedEventHandler(scriptDownload_DownloadStringCompleted);
                        scriptDownload.DownloadStringAsync(src, new string[] { lang });
                    }
                }
            }

        }

        void scriptDownload_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            string scriptBody = e.Result;
            ExecuteCode(GetScope(((string[])e.UserState)[0], englist), scriptBody);
        }

        ScriptScope GetScope(string lang, Dictionary<object, object> englist)
        {
            var engines = englist;

            ScriptScope scope;

            if (engines.ContainsKey(lang))
            {
                scope = (ScriptScope)engines[lang];
                System.Diagnostics.Debug.WriteLine("Scope for " + lang + " existed!");
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("Creating scope for " + lang + "!");

                var setup = Configuration.LoadFromAssemblies(Package.GetManifestAssemblies());

                //setup.PrivateBinding = true;

                setup.HostType = typeof(BrowserScriptHost);

                //if (System.Diagnostics.Debugger.IsAttached)
                setup.DebugMode = true;

                ScriptRuntime r = new ScriptRuntime(setup);

                ScriptEngine eng = r.GetEngine(lang);

                eng.Runtime.LoadAssembly(typeof(string).Assembly);
                eng.Runtime.LoadAssembly(typeof(System.Diagnostics.Debug).Assembly);
                eng.Runtime.LoadAssembly(typeof(Canvas).Assembly);
                eng.Runtime.LoadAssembly(typeof(HtmlPage).Assembly);
                
                eng.Runtime.LoadAssembly(this.GetType().Assembly);
                eng.Runtime.LoadAssembly(typeof(WebClient).Assembly);
                eng.Runtime.LoadAssembly(typeof(JsonValue).Assembly);
                eng.Runtime.LoadAssembly(typeof(Random).Assembly);
                eng.Runtime.LoadAssembly(typeof(DateTime).Assembly);
                eng.Runtime.LoadAssembly(typeof(System.Windows.Threading.DispatcherTimer).Assembly);

                scope = eng.CreateScope();
                scope.SetVariable("me", this);
                scope.SetVariable("xaml", this);
                scope.SetVariable("document", HtmlPage.Document);
                scope.SetVariable("window", HtmlPage.Window);

                string init = "";

                if (lang == "ruby")
                {
                    init = @"
include System::Windows::Controls
include System::Windows::Browser
include System::Windows::Media
include System::Windows
include System::Json
include Gestalt

class App
    def method_missing(methodname)
      self.get_Item(methodname.to_s)
    end
end

class HtmlDocument
  def method_missing(m, *args)
    super
  rescue => e
    id = get_element_by_id(m)
    return id unless id.nil?
    raise e  
  end

  alias_method :orig_get_element_by_id, :get_element_by_id
  def get_element_by_id(id)
    orig_get_element_by_id(id.to_s.to_clr_string)
  end
  
  alias_method :orig_get_elements_by_tag_name, :get_elements_by_tag_name
  def get_elements_by_tag_name(name)
    orig_get_elements_by_tag_name(name.to_s.to_clr_string)
  end
  
  def tags(name)
    get_elements_by_tag_name name
  end
end

class HtmlElementCollection
  def [](index)
    get_Item(index)
  end
  def size
    count
  end
  def first
    self[0] if size > 0
  end
  def last
    self[size - 1] if size > 0
  end
  def empty?
    size == 0
  end
end

class HtmlElement
  
  def [](index)
    val = get_attribute(index)
    return get_property(index) if val.nil?
    return val
  end

  def []=(index, value)
    val = get_attribute(index)
    val.nil? ? set_property(index, value) : set_attribute(index, value)
  end

  def method_missing(m, *args, &block)
    super
  rescue => e
    if block.nil?
      if m.to_s[-1..-1] == ""=""
        self[m.to_s[0..-2]] = args.first
      else
        id = self[m] 
        return id unless id.nil?
        raise e
      end
    else
      unless attach_event(m.to_s.to_clr_string, System::EventHandler.of(HtmlEventArgs).new(&block))
        raise e
      end
    end
  end

  def style
    HtmlStyle.new(self)
  end

  alias_method :orig_get_attribute, :get_attribute
  def get_attribute(index)
    orig_get_attribute(index.to_s.to_clr_string)
  end

  alias_method :orig_set_attribute, :set_attribute
  def set_attribute(index, value)
    orig_set_attribute(index.to_s.to_clr_string, value)
  end

  alias_method :orig_get_property, :get_property
  def get_property(index)
    orig_get_property(index.to_s.to_clr_string)
  end

  alias_method :orig_set_property, :set_property
  def set_property(index, value)
    orig_set_property(index.to_s.to_clr_string, value)
  end

  alias_method :orig_get_style_attribute, :get_style_attribute
  def get_style_attribute(index)
    orig_get_style_attribute(index.to_s.to_clr_string)
  end

  alias_method :orig_set_style_attribute, :set_style_attribute
  def set_style_attribute(index, value)
    orig_set_style_attribute(index.to_s.to_clr_string, value)
  end

end

class HtmlStyle
  def initialize(element)
    @element = element
  end

  def [](index)
    @element.get_style_attribute(index)
  end 

  def []=(index, value)
    @element.set_style_attribute(index, value)
  end

  def method_missing(m, *args)
    super
  rescue => e
    if m.to_s[-1..-1] == ""=""
      self[m.to_s[0..-2]] = args.first
    else
      style = self[m]
      return style unless style.nil?
      raise e
    end
  end
end

";
                }
                else if (lang == "python")
                {
                    init = @"
from System.Windows.Controls import *
from System.Windows.Browser import *
from System.Windows.Media import *
from System.Windows import *
from System import EventHandler
from System.Json import *

";
                }

                // Execute the initialization code
                ScriptSource initSource = eng.CreateScriptSourceFromString(init, SourceCodeKind.Statements);
                initSource.Execute(scope);

                // Add the scope to dictionare
                engines.Add(lang, scope);

            }

            return scope;

        }

        void ExecuteCode(ScriptScope scope, string scriptbody)
        {

            ScriptSource source = scope.Engine.CreateScriptSourceFromString(scriptbody, SourceCodeKind.Statements);
            source.Execute(scope);

        }

    }
}
